package scenic;

import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.util.Observable;

/**
 * The Image class represents an image. Image objects are used
 * to display images. The image data is stored in video memory. 
 * 
 * The format of the image data is specified by the format 
 * attribute. Different formats are defined in the Format
 * class.
 */
public class Image extends Observable {
	private int id;
	private int width;
	private int height;
	private int format;
	int options;

	/** Constructs an empty image
	 */
	public Image() {
		create(0, 0, 0);
	}
	
	/**
	 * Constructs an image with given parameters. 
	 * 
	 * @param width the width of the image.
	 * @param height the height of the image.
	 * @param format the format of the image (must be one of the 
	 * constants defined in the Format class).
	 */
	public Image(int width, int height, int format) {
		create(width, height, format);
	}
	
	public Image(int width, int height, int format, int options) {
		create(width, height, format, options);
	}
	
	/**
	 * Creates an image from the given BufferedImage. The buffered image must be of
	 * type TYPE_3BYTE_BGR.
	 * 
	 * @param image the source image.
	 */
	public Image(BufferedImage image) {
		if(image.getType() != BufferedImage.TYPE_3BYTE_BGR) {
			throw new IllegalArgumentException("Unsupported image type");
		}
		DataBuffer buffer = image.getRaster().getDataBuffer();
		DataBufferByte byteBuffer = (DataBufferByte)buffer;		
		
		create(image.getWidth(), image.getHeight(), Format.R8G8B8);		
		write(0, 0, width, height, byteBuffer.getData(), Format.R8G8B8);
	}
	
	/**
	 * Converts the image to a java.awt.image.BufferedImage object.
	 * The type of the buffered image is BufferedImage.TYPE_4BYTE_ABGR.
	 * 
	 * @return the BufferedImage object
	 */
	public BufferedImage convertToBufferedImage() {
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
		DataBuffer buffer = image.getRaster().getDataBuffer();
		DataBufferByte byteBuffer = (DataBufferByte)buffer;		
		
		read(0, 0, width, height, byteBuffer.getData(), Format.R8G8B8A8);
		return image;
	}
	
	protected void finalize() {
		free();
	}
	
	/**
	 * Gets the width of the image.
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * Gets the height of the image.
	 */
	public int getHeight() {
		return height;
	}
	
	/**
	 * Gets the format of the image. The format is one of the constants
	 * defined in the Format class.
	 */
	public int getFormat() {
		return format;
	}
	
	/**
	 * Creates an image with the given parameters. The new image replaces
	 * the old image. Pixel data is not copied to the new image.
	 * 
	 * @param width the width of the image.
	 * @param height the height of the image.
	 * @param format the format of the image (must be one of the 
	 * constants defined in the Format class).
	 * @param options options for the image (must be one of the 
	 * constants defined in the ImageOptions class).
	 */
	public void create(int width, int height, int format, int options) {
		free();
		this.width = width;
		this.height = height;
		this.format = format;
		this.options = options;
		id = nativeCreate(width, height, format, options);
		markChanged();
	}

	/**
	 * Creates an image with the given parameters. The new image replaces
	 * the old image. Pixel data is not copied to the new image.
	 * 
	 * @param width the width of the image.
	 * @param height the height of the image.
	 * @param format the format of the image (must be one of the 
	 * constants defined in the Format class).
	 */
	public void create(int width, int height, int format) {
		create(width, height, format, 0);
	}
	
	private void markChanged() {
		setChanged();
		notifyObservers();
		clearChanged();		
	}
	
	/**
	 * Checks if the contents of the image are lost. Some image types
	 * are volatile, meaning that their contents can be lost. Images that
	 * are used as render targets can be lost when using DirectX. On OpenGL
	 * images are never lost. For compatibility, you should always check
	 * if the contents are lost when you are using render target images.
	 * 
	 * @return true if the contents of the image are lost
	 */
	public boolean contentsLost() {
		return nativeContentsLost(id);
	}

	int getId() {
		return id;
	}
	
	/**
	 * Frees the resources associated with the image.
	 */
	public void free() {
		this.width = 0;
		this.height = 0;
		this.format = 0;
		
		if(id != 0) {
			nativeFree(id);
			id = 0;
		}	
		markChanged();
	}
	
	/**
 	 * Writes the pixel data of the image in the given rectangle.
 	 * 
	 * @param x the left side of the rectangle
	 * @param y the top of the rectangle
	 * @param width the width of the rectangle
	 * @param height the height of the rectangle
	 * @param data the pixel data
	 * @param format format of the pixel data
	 */
	public void write(int x, int y, int width, int height, byte[] data, int format) {
		nativeWriteByte(id, x, y, width, height, data, 0, 0, format);
		markChanged();
	}
	
	/**
 	 * Writes the pixel data of the image in the given rectangle.
 	 * 
	 * @param x the left side of the rectangle
	 * @param y the top of the rectangle
	 * @param width the width of the rectangle
	 * @param height the height of the rectangle
	 * @param data the pixel data
	 * @param format format of the pixel data
	 */
	public void write(int x, int y, int width, int height, int[] data, int format) {
		nativeWriteInt(id, x, y, width, height, data, 0, 0, format);
		markChanged();
	}
	
	/**
 	 * Writes the pixel data of the image in the given rectangle.
 	 * 
	 * @param x the left side of the rectangle
	 * @param y the top of the rectangle
	 * @param width the width of the rectangle
	 * @param height the height of the rectangle
	 * @param data the pixel data
	 * @param format format of the pixel data
	 */
	public void write(int x, int y, int width, int height, float[] data, int format) {
		nativeWriteFloat(id, x, y, width, height, data, 0, 0, format);
		markChanged();
	}
	
	/**
 	 * Reads the pixel data of the image in the given rectangle.
 	 * 
	 * @param x the left side of the rectangle
	 * @param y the top of the rectangle
	 * @param width the width of the rectangle
	 * @param height the height of the rectangle
	 * @param data the pixel data
	 * @param format format of the pixel data
	 */
	public void read(int x, int y, int width, int height, byte[] data, int format) {
		nativeReadByte(id, x, y, width, height, data, 0, 0, format);
	}
	
	/**
 	 * Reads the pixel data of the image in the given rectangle.
 	 * 
	 * @param x the left side of the rectangle
	 * @param y the top of the rectangle
	 * @param width the width of the rectangle
	 * @param height the height of the rectangle
	 * @param data the pixel data
	 * @param format format of the pixel data
	 */
	public void read(int x, int y, int width, int height, int[] data, int format) {
		nativeReadInt(id, x, y, width, height, data, 0, 0, format);
	}
	
	/**
 	 * Reads the pixel data of the image in the given rectangle.
 	 * 
	 * @param x the left side of the rectangle
	 * @param y the top of the rectangle
	 * @param width the width of the rectangle
	 * @param height the height of the rectangle
	 * @param data the pixel data
	 * @param format format of the pixel data
	 */
	public void read(int x, int y, int width, int height, float[] data, int format) {
		nativeReadFloat(id, x, y, width, height, data, 0, 0, format);
	}
	
	private native int nativeCreate(int width, int height, int format, int options);
	private native void nativeFree(int id);
	private native boolean nativeContentsLost(int id);
	private native void nativeWriteByte(int id, int x, int y, int width, int height, byte[] data, int offset, int pitch, int format);	
	private native void nativeWriteInt(int id, int x, int y, int width, int height, int[] data, int offset, int pitch, int format);	
	private native void nativeWriteFloat(int id, int x, int y, int width, int height, float[] data, int offset, int pitch, int format);	
	private native void nativeReadByte(int id, int x, int y, int width, int height, byte[] data, int offset, int pitch, int format);
	private native void nativeReadInt(int id, int x, int y, int width, int height, int[] data, int offset, int pitch, int format);
	private native void nativeReadFloat(int id, int x, int y, int width, int height, float[] data, int offset, int pitch, int format);
}
